package com.atguigu.gmall.cart.service.impl;

import com.atguigu.gmall.cart.mapper.CartInfoMapper;
import com.atguigu.gmall.cart.service.CartAsyncService;
import com.atguigu.gmall.cart.service.CartInfoService;
import com.atguigu.gmall.common.constant.RedisConst;
import com.atguigu.gmall.common.util.DateUtil;
import com.atguigu.gmall.model.cart.CartInfo;
import com.atguigu.gmall.model.product.SkuInfo;
import com.atguigu.gmall.product.client.ProductFeignClient;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

import java.sql.Timestamp;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * @Author LEO
 * @Date 2021年12月17日 9:09
 */
@Service
public class CartInfoServiceImpl implements CartInfoService {

    @Autowired
    private CartInfoMapper cartInfoMapper;

    @Autowired
    private CartAsyncService cartAsyncService;

    @Autowired
    private ProductFeignClient productFeignClient;

    @Autowired
    private RedisTemplate redisTemplate;
    /**
     *   添加购物车
     * @param skuId
     * @param userId
     * @param skuNum
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void addToCart(Long skuId, String userId, Integer skuNum) {
        //addToCartOne(skuId,userId,skuNum);

        // 添加数据之前
        // 查询一下缓存是否有购物车的key ! 如果没有这个key，则说明缓存过期，加载到缓存！ 如果有，说明这个缓存的数据是全的

        //获取到购物车的key
        String cartKey= this.getCartKey(userId);
        // 购物车的key  在缓存中不存在
        if(!redisTemplate.hasKey(cartKey)){
            // 加载数据库中的数据到缓存   后面再新增一条数据就全了
            this.loadCartCache(userId);
        }

        CartInfo cartInfoExit =null;
        /**
         * 判断购物是否有该商品
         * true: 数量相加
         * false: insert
         */
        try {
            //代码走到这，说明缓存一定有数据了  hget key filed
            //cartInfoExit = (CartInfo) redisTemplate.boundHashOps(cartKey).get(skuId.toString());
            cartInfoExit= (CartInfo) redisTemplate.opsForHash().get(cartKey,skuId.toString());

            //select * from  cart_info where sku_id=skuId and user_id=userId
//            QueryWrapper<CartInfo> queryWrapper = new QueryWrapper<>();
//            queryWrapper.eq("sku_id",skuId);
//            queryWrapper.eq("user_id",userId);
//            cartInfoExit = cartInfoMapper.selectOne(queryWrapper);
            if (cartInfoExit != null){
                // 数量相加
                cartInfoExit.setSkuNum(cartInfoExit.getSkuNum()+skuNum);
                //赋值商品实时价格
                cartInfoExit.setSkuPrice(productFeignClient.getSkuPrice(skuId));
                //重新设置修改时间
                cartInfoExit.setUpdateTime(new Timestamp(new Date().getTime()));
                // 再次选中
                cartInfoExit.setIsChecked(1);
                // 赋值更新语句  cartInfoMapper.selectOne  有id
//                cartInfoMapper.updateById(cartInfoExit);
                cartAsyncService.updateCartInfo(cartInfoExit);
                // 添加数据时直接添加到缓存

            }else {
                // insert
                SkuInfo skuInfo = productFeignClient.getSkuInfo(skuId);
                CartInfo cartInfo = new CartInfo();
                cartInfo.setUserId(userId);
                cartInfo.setSkuId(skuId);
                cartInfo.setCartPrice(skuInfo.getPrice());
                cartInfo.setSkuPrice(skuInfo.getPrice());
                cartInfo.setSkuNum(skuNum);
                cartInfo.setSkuName(skuInfo.getSkuName());
                cartInfo.setImgUrl(skuInfo.getSkuDefaultImg());
                cartInfo.setCreateTime(new Timestamp(new Date().getTime()));
                cartInfo.setUpdateTime(new Timestamp(new Date().getTime()));
//                cartInfoMapper.insert(cartInfo);
                cartAsyncService.saveCartInfo(cartInfo);//异步操作此时还没有执行insert 所以此时没有cartInfo.id,缓存之后才insert
                // 执行insert into 的时候出错 ,则redis会继续执行

                cartInfoExit=cartInfo;
                // 添加数据时直接添加到缓存
            }
        } catch (Exception e) {
            e.printStackTrace();
            return;
        }
        //添加购物车到缓存
        redisTemplate.opsForHash().put(cartKey,skuId.toString(),cartInfoExit);
        this.setCartKeyExpire(cartKey);

    }

    /**
     * 根据用户id,临时用户id查询购物车列表
     * @param uid
     * @param userTempId
     * @return
     */
    @Override
    public List<CartInfo> getCarList(String uid, String userTempId) {
        List<CartInfo> cartInfosList=new ArrayList<>();
        /**
         * 1. 先查询缓存
         * 2. 判断查询结果
         *     true: 返回
         *     false: 查询数据库并将数据放入缓存
         */

        //查询登录时的购物车
        if(!StringUtils.isEmpty(uid)){
            // 有可能会发生合并购物车
            List<CartInfo> cartList = new ArrayList<>();
            // 获取临时购物车数据
            if(StringUtils.isEmpty(userTempId)){
                // 停止了
                // 临时购物车空，只查询登录购物车数据
                cartInfosList= this.getCartList(uid);
                return cartList;
            }else {
                cartList = this.getCartList(userTempId);
            }

            //判断临时购物车获取到的集合数据
            if (!CollectionUtils.isEmpty(cartList)){
                // 说明未登录购物车有数据,需要合并
                cartInfosList= this.mergeToCarList(cartList,uid);
                // 删除临时购物车数据
                this.deleteCartList(userTempId);
            }else {
                // 临时购物车空，只查询登录购物车数据
                cartInfosList= this.getCartList(uid);
            }
        }
        //查询临时购物车数据
        if(StringUtils.isEmpty(uid)){
            cartInfosList= this.getCartList(userTempId);
        }
        return cartInfosList;
    }

    // 选中状态变更
    @Override
    public void checkCart(String userId, Integer isChecked, Long skuId) {
        // 操作数据库
        cartAsyncService.checkCart(userId,isChecked,skuId);
        // 同步redis
        String cartKey = this.getCartKey(userId);
        // 获取修改的cartInfo hget key field   field=skuId
        CartInfo cartInfo= (CartInfo) redisTemplate.opsForHash().get(cartKey,skuId.toString());
        if (cartInfo != null){
            //变更状态
            cartInfo.setIsChecked(isChecked);
            // 将变更之后的数据放回缓存 hset key field value
             redisTemplate.opsForHash().put(cartKey,skuId.toString(),cartInfo);
            // 可以考虑是否需要重新设置过期时间
            this.setCartKeyExpire(cartKey);
        }

        // 方式2.判断当前购物车是否有当前数据
//       boolean flag = redisTemplate.boundHashOps(cartKey).hasKey(skuId.toString());
//       if (flag){
//           // 有数据
//           CartInfo cartInfo= (CartInfo) redisTemplate.boundHashOps(cartKey).get(skuId.toString());
//           // 变更状态
//           cartInfo.setIsChecked(isChecked);
//           // 将变更之后的数据放回缓存 hset key field value
//             redisTemplate.opsForHash().put(cartKey,skuId.toString(),cartInfo);
//           // 可以考虑是否需要重新设置过期时间
//            this.setCartKeyExpire(cartKey);
//       }
    }

    //删除购物项
    @Override
    public void deleteCartInfo(String userId, Long skuId) {
        // 删除数据库
        cartAsyncService.deleteCartInfo(userId,skuId);
        //删除缓存
        String cartKey = this.getCartKey(userId);
        //删除
        Boolean result = redisTemplate.boundHashOps(cartKey).hasKey(skuId.toString());
        if (result){
            // 删除购物车某一项
            redisTemplate.boundHashOps(cartKey).delete(skuId.toString());
        }


    }

    /**
     * 根据用户Id 查询购物车列表
     *
     * @param userId
     * @return
     */
    @Override
    public List<CartInfo> getCartCheckedList(String userId) {
        // 数据从哪来 缓存
        String cartKey = this.getCartKey(userId);
        //获取购物车数据接口
        List<CartInfo> cartInfoList = redisTemplate.boundHashOps(cartKey).values();
        // 判断选中
        List<CartInfo> cartInfos = cartInfoList.stream().filter(cartInfo -> {
            return cartInfo.getIsChecked().intValue() == 1;
        }).collect(Collectors.toList());
        return cartInfos;
    }


    // 删除临时购物车数据
    private void deleteCartList(String userTempId) {
        // 删除数据库  redis
        // delete from cart_info where user_id=userTempId
//        QueryWrapper<CartInfo> queryWrapper = new QueryWrapper<>();
//       queryWrapper.eq("user_id",userTempId);
//       cartInfoMapper.delete(queryWrapper);
            // 异步删除
//        CartInfo cartInfo = new CartInfo();
//        cartInfo.setUserId(userTempId);
//        cartAsyncService.deleteCartInfo(cartInfo);
        // 异步删除
        cartAsyncService.deleteCartInfoByUserId(userTempId);

       // redis.del(key)
        String cartKey = this.getCartKey(userTempId);
        if (redisTemplate.hasKey(cartKey)){
            redisTemplate.delete(cartKey);
        }
    }

    // 合并购物车
    private List<CartInfo> mergeToCarList(List<CartInfo> cartInfoNoLoginList, String uid) {
        /**
         * 1.根据用户id获取登录后购物车数据
         * 2.做合并处理，处理条件 sku相同 数量相加
         *  登录：
         *             37 1
         *             38 1
         *         未登录：
         *             37 1
         *             38 1
         *             39 1
         *         合并之后的数据
         *             37 2
         *             38 2
         *             39 1
         * 相同：update
         * 没有相同：insert
         * 将最终的合并结果查询并返回
         */
        List<CartInfo> cartInfoLoginList =  this.getCartList(uid);
        //  第一种方案：双重for 遍历：根据skuId 是否相同！
        //  第二种方案：使用map做包含 cartInfoLoginList 变成map 集合 key = skuId ,value = CartInfo
        Map<Long, CartInfo> longCartInfoMap = cartInfoLoginList.stream().collect(Collectors.toMap(CartInfo::getSkuId, cartInfo -> {
            return cartInfo;
        }));
//  Map<Long, CartInfo> longCartInfoMap = cartInfoLoginList.stream().collect(Collectors.toMap(CartInfo::getSkuId, cartInfo -> cartInfo));
        //  遍历临时的购物车数据
        for (CartInfo cartInfo : cartInfoNoLoginList) {
            //  未登录购物车购物项的skuId
            Long skuId = cartInfo.getSkuId();
            if (longCartInfoMap.containsKey(skuId)){
                //  数量相加 37,38 数量相加
                CartInfo cartLoginInfo = longCartInfoMap.get(skuId);
                cartLoginInfo.setSkuNum(cartLoginInfo.getSkuNum()+cartInfo.getSkuNum());
                //判断未登录购物车的选中状态
                if (cartInfo.getIsChecked().intValue()==1){
                    //选中状态
                    cartLoginInfo.setIsChecked(1);

                }
                //更新数据库  有可能出现id为空
               // cartInfoMapper.updateById(cartLoginInfo);
                // userId skuId 异步操作
                UpdateWrapper<CartInfo> updateWrapper = new UpdateWrapper<>();
                updateWrapper.eq("user_id",cartLoginInfo.getUserId());
                updateWrapper.eq("sku_id",cartLoginInfo.getSkuId());
                cartInfoMapper.update(cartLoginInfo,updateWrapper);
            }else {
                //插入数据库
                // //  但是临时购物车数据的userId 是类似于一个uuid 的东西！
                cartInfo.setUserId(uid);
                cartInfo.setCreateTime(new Timestamp(new Date().getTime()));
                cartInfo.setUpdateTime(new Timestamp(new Date().getTime()));
//                cartAsyncService.saveCartInfo(cartInfo);  // 后执行！    建议同步！ 建议redis 没有过期时间！
                cartInfoMapper.insert(cartInfo);
            }
        }
        // 最终要返回 37,38,39 :  把数据的数据库拿出来放缓存
        List<CartInfo> cartInfoList= loadCartCache(uid);    // 查询走缓存
        return cartInfoList ;
    }

    //查询购物车列表
    public List<CartInfo> getCartList(String uid){
        //查缓存查数据
        String cartKey = this.getCartKey(uid);
        //hash 获取数据 hvals filed   hvals user:1:cart
        List<CartInfo> list = redisTemplate.opsForHash().values(cartKey);
        if (CollectionUtils.isEmpty(list)){
            //缓存没有数据，从数据库获取并放入缓存
            list=this.loadCartCache(uid);
//            return list;
        }
        //缓存有数据
            //排序
            list.sort(new Comparator<CartInfo>() {
                @Override
                public int compare(CartInfo o1, CartInfo o2) {
                    //按照修改时间排序  后来的-前面的  o2.getUpdateTime()-o1.getUpdateTime();
                    return DateUtil.truncatedCompareTo(o2.getUpdateTime(),o1.getUpdateTime(), Calendar.SECOND);
                }
            });
            return list;
    }

    // 从数据库查询购物车列表
    public List<CartInfo> loadCartCache(String uid) {
        //当缓存中的购物车数据为空时，当前的购物车价格可能会发生变动  ，所以要查询一下实时价格
        // select * from cart_info where user_id=1
        QueryWrapper<CartInfo> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("user_id",uid);
        queryWrapper.orderByDesc("update_time");
        List<CartInfo> cartInfoList = cartInfoMapper.selectList(queryWrapper);
        //如果数据库中数据为空
        if (StringUtils.isEmpty(cartInfoList)){
            return new ArrayList<CartInfo>();
        }
        // 获取到当前购物车key
        String cartKey = this.getCartKey(uid);
        HashMap<String, CartInfo> map = new HashMap<>();
        //遍历当前购物车集合，放入缓存，同时将skuPrice 赋值
        for (CartInfo cartInfo : cartInfoList) {
            //查询一下实时价格
            cartInfo.setSkuPrice(productFeignClient.getSkuPrice(cartInfo.getSkuId()));
//            redisTemplate.opsForHash().put(cartKey,cartInfo.getSkuId().toString(),cartInfo);
            map.put(cartInfo.getSkuId().toString(),cartInfo);
        }
        // 放入缓存
        redisTemplate.opsForHash().putAll(cartKey,map);
        //设置一个过期时间
        this.setCartKeyExpire(cartKey);

        return cartInfoList;
    }



    //获取购物车的key
    public String getCartKey(String userId){
        // 整个key  数据类型   hash 因为要修改 hset key filed value   hget key filed          商品详情页string  只做查询
        // cartKey=user:userId:cart   查看谁的购物车
        // filed =skuId   value=cartInfo
        return  RedisConst.USER_KEY_PREFIX+userId+RedisConst.USER_CART_KEY_SUFFIX;
    }

    // 购物车过期时间
    public void setCartKeyExpire(String cartKey){
        //给购物车设置一个过期时间
        redisTemplate.expire(cartKey,RedisConst.USER_CART_EXPIRE,TimeUnit.SECONDS);

    }


}
